package org.budo.support.java.net.util;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;

import org.budo.support.java.collection.wrapper.NiceToStringListWrapper;
import org.budo.support.lang.util.ListUtil;
import org.budo.support.lang.util.StringUtil;
import org.budo.support.slf4j.Slf4j;
import org.slf4j.Logger;

/**
 * @author limingwei
 * @date 2016年6月7日 下午2:59:11
 */
public class JavaNetUtil {
    private static final Logger log = Slf4j.getLogger();

    /**
     * 本机网卡缓存，避免频繁获取；频繁调用接口获取方法易导致 SocketException Too many open files 问题
     */
    private static List<NetworkInterface> NETWORK_INTERFACES_CACHE = null;

    private static String hostInnerFirst_1;

    public static List<NetworkInterface> findNetworkInterfaces() {
        try {
            Enumeration<NetworkInterface> enumeration = NetworkInterface.getNetworkInterfaces();
            return ListUtil.enumerationToList(enumeration);
        } catch (SocketException e) {
            throw new RuntimeException(e);
        }
    }

    public static List<NetworkInterface> getNetworkInterfaces() {
        if (null == NETWORK_INTERFACES_CACHE) {
            NETWORK_INTERFACES_CACHE = findNetworkInterfaces();
        }

        return NETWORK_INTERFACES_CACHE;
    }

    public static Boolean reachable(String host, Integer port) {
        InetSocketAddress inetSocketAddress = new InetSocketAddress(host, port);
        return !inetSocketAddress.isUnresolved();
    }

    private static InetAddress getLocalHost() {
        try {
            return InetAddress.getLocalHost();
        } catch (Throwable e) {
            if (!("" + e).contains("UnknownHostException")) {
                log.error("#33 getLocalHost error, e=" + e);
            }

            // UnknownHostException
            return null;
        }
    }

    public static String getLocalHostName() {
        InetAddress localHost = getLocalHost();

        if (null == localHost) {
            return getHostInnerFirst();
        }

        String hostName = localHost.getHostName();
        if (!StringUtil.isEmpty(hostName)) {
            return hostName;
        }

        String canonicalHostName = localHost.getCanonicalHostName();
        if (!StringUtil.isEmpty(canonicalHostName)) {
            return canonicalHostName;
        }

        return getHostInnerFirst();
    }

    public static String getHostInnerFirst() {
        List<String> networkInterfaceAddresses = getNetworkInterfaceAddresses();

        if (null == networkInterfaceAddresses || networkInterfaceAddresses.isEmpty()) {
            networkInterfaceAddresses = new ArrayList<String>();
            networkInterfaceAddresses.add("get_host_inner_first_ips_null");
        }

        Collections.sort(networkInterfaceAddresses, HostInnerFirstComparator.INSTANCE);

        return ListUtil.first(networkInterfaceAddresses);
    }

    public static boolean isLoclePortUsing(int port) {
        String host = "127.0.0.1";
        return isPortUsing(host, port);
    }

    public static boolean isPortUsing(String host, int port) {
        try {
            InetAddress inetAddress = InetAddress.getByName(host);
            InetSocketAddress socketAddress = new InetSocketAddress(inetAddress, port);

            Socket socket = new Socket();
            socket.connect(socketAddress, 200);
            socket.close();
            return true;
        } catch (Throwable e) {
            // log.error("#112 isLoclePortUsing, e=" + e);
            return false;
        }
    }

    /**
     * 会包括 docker0 网卡地址 类似 172.17.42.1
     */
    public static List<String> getNetworkInterfaceAddresses() {
        List<NetworkInterface> networkInterfaces = getNetworkInterfaces();
        if (null == networkInterfaces) {
            return null;
        }

        List<String> networkInterfaceAddresses = new ArrayList<String>();
        for (NetworkInterface networkInterface : networkInterfaces) {
            if (null == networkInterface) {
                continue;
            }

            Enumeration<InetAddress> inetAddresses = networkInterface.getInetAddresses();
            while (inetAddresses.hasMoreElements()) {
                InetAddress inetAddress = inetAddresses.nextElement();
                String hostAddress = inetAddress.getHostAddress();
                networkInterfaceAddresses.add(hostAddress);
            }
        }

        return new NiceToStringListWrapper(networkInterfaceAddresses);
    }

    /**
     * 网卡ip地址列表，排除127，排除v6地址
     */
    public static List<String> getIpAddresses() {
        List<NetworkInterface> networkInterfaces = getNetworkInterfaces();
        if (null == networkInterfaces) {
            return null;
        }

        List<String> networkInterfaceAddresses = new ArrayList<String>();
        for (NetworkInterface networkInterface : networkInterfaces) {
            if (null == networkInterface) {
                continue;
            }

            Enumeration<InetAddress> inetAddresses = networkInterface.getInetAddresses();
            while (inetAddresses.hasMoreElements()) {
                InetAddress inetAddress = inetAddresses.nextElement();
                String hostAddress = inetAddress.getHostAddress();
                if (null == hostAddress) {
                    continue;
                }

                if (hostAddress.length() > "xxx.xxx.xxx.xxx".length()) {
                    continue;
                }

                if (hostAddress.trim().startsWith("127.")) {
                    continue;
                }

                if (hostAddress.trim().startsWith("0:0:0:0:0:0:0:")) {
                    continue;
                }

                networkInterfaceAddresses.add(hostAddress);
            }
        }

        return new NiceToStringListWrapper(networkInterfaceAddresses);
    }

    public static String getEth0Ip() {
        String eth0Ip = null;

        List<NetworkInterface> networkInterfaces = getNetworkInterfaces();
        if (null != networkInterfaces) {
            for (NetworkInterface networkInterface : networkInterfaces) {
                if (null == networkInterface) {
                    continue;
                }

                String networkInterfaceName = networkInterface.getName();
                if (null == networkInterfaceName || !networkInterfaceName.trim().equalsIgnoreCase("eth0")) {
                    continue;
                }

                List<InterfaceAddress> interfaceAddresses = networkInterface.getInterfaceAddresses();
                for (InterfaceAddress interfaceAddress : interfaceAddresses) {
                    InetAddress address = interfaceAddress.getAddress();
                    if (null == address) {
                        continue;
                    }

                    String hostAddress = address.getHostAddress();
                    if (null == hostAddress) {
                        continue;
                    }

                    if (hostAddress.length() > "xxx.xxx.xxx.xxx".length()) {
                        continue;
                    }

                    if (hostAddress.trim().startsWith("127.")) {
                        continue;
                    }

                    eth0Ip = hostAddress;
                }
            }
        }

        if (StringUtil.isEmpty(eth0Ip)) {
            log.error("#180 eth0Ip is null or empty, eth0Ip=" + eth0Ip + ", ipAddresses=" + getIpAddresses());
            eth0Ip = getHostInnerFirst();
        }

        if (StringUtil.isEmpty(eth0Ip)) {
            log.error("#185 getHostInnerFirst is null or empty, getHostInnerFirst=" + eth0Ip + ", ipAddresses=" + getIpAddresses());
        }

        return eth0Ip;
    }

    public static InetAddress getInetAddressByName(String host) {
        try {
            return InetAddress.getByName(host);
        } catch (UnknownHostException e) {
            throw new RuntimeException(e);
        }
    }

    public static String getHostInnerFirst_1() {
        if (null != hostInnerFirst_1) {
            return hostInnerFirst_1;
        }

        return hostInnerFirst_1 = getHostInnerFirst();
    }
}